תכניות סטנדרטיות ב UNIX שרשור פקודות באמצעות Pipeline עבודה ב- bash
הרכבת פקודות Pipeline 2
נניח שברצוננו להדפיס את התוכן של תיקיה המכילה הרבה קבצים לא נוכל במצב זה לראות את כל הקבצים נוכל להשתמש בהפנית פלט הפקודה ls לקובץ זמני ולאחר מכן שימוש ב- more : > ls -l > tmp > more < tmp > rm tmp 3
כדי לחסוך את הבלגן שבטיפול בקבצים זמניים ניתן להשתמש ב- pipeline המחבר את הפלט של פקודה אחת לקלט של פקודה שניה ישירות כדי לחבר שתי פקודות כך נרשום אותן אחת אחרי השניה כאשר ביניהן מפריד התו על מנת לראות בשלבים את התוכן של תיקיה גדולה נוכל לרשום כעת: ניתן לשלב מספר תכניות בבת אחת: > ls -l more > command1 command2 command3 4
ניתן להשתמש בתו כדי לחבר את הפלט של תכנית א' עם הקלט של תכנית ב' בעזרת pipeline נוכל להרכיב פקודות מורכבות בעבודה בטרמינל מתכניות פשוטות 5
bash מהו?bash תכונות מתקדמות של עבודת ה- bash 6
Shell )קליפה( הוא כינוי לתוכנה המקשרת בין המשתמש לבין גרעין מערכת ההפעלה בדרך כלל המונח בשימוש עבור shell טקסטואלי עבודת ה- Shell מתבצעת על ידי קבלת פקודה מהמשתמש, ביצוע החלפות טקסט בפקודה בהתאם לתכונות ה- Shell ולבסוף שליחת הפקודה המעובדת למערכת ההפעלה דוגמאות ל- shell : Bash C-Shell Powershell )ברירת המחדל ברוב הפצות הלינוקס( )ברירת המחדל עבור מחשב ה )stud )windows מתקדם עבור shell( בקורס זה נלמד את Bash מלבד מנשק בסיסי לביצוע פקודות ה- Shell מקל עלינו את העבודה בעזרת תכונות מתקדמות, לדוגמה ההשלמה האוטומטית המתבצעת ע"י לחיצה על Tab ה- Shell יאפשר לנו להגדיר קיצורים, לחזור על פקודות, להתייחס למספר קבצים בנוחות ולהגדיר משתנים אשר נוכל להשתמש בהם בפקודות 7
ניתן להשתמש בפקודה הבאה כדי לקבל את שם ה- Shell הנוכחי ב- Unix > echo $0 tcsh במחשבים בהם Bash אינה Shell ברירת המחדל )כמו בשרת ה- stud לדוגמה(, ניתןלהשתמש בפקודה shell) chsh (change כדי לשנות את ברירת המחדל: > chsh Changing shell for mtm. old shell: /bin/tcsh New shell: /bin/bash Shell will be changed for mtm in approximately 5 minutes > חשוב להקפיד לכתוב את שם ה- Shell )הייחוס אליו( נכון, אחרת החשבון נהרס מלבד החלפת ה- Shell המשמש כברירת מחדל, ניתן תמיד להריץ Shell אחר כמו כל פקודה אחרת 8
במקרים רבים נרצה להדפיס לפלט מחרוזת, לשם כך ניתן להשתמש בפקודה echo הפקודה echo מדפיסה את הפרמטרים שקיבלה > echo [-n] [words] n-: מדפיס ללא ירידת שורה > echo Hello world! Hello world! > > echo n Hello world! Hello world! > 9
הפקודה alias מאפשרת להגדיר קיצורים ב- Shell > alias <new name>="<command + parameters>" אסור לשים רווחים בסמוך לתו '=' לאחר הרצת הפקודה נוכל לרשום את הפקודה name> <new כקיצור לפקודה <command> עם הפרמטרים שהוספנו. ה- Shell יבדוק בשלב עיבור הפקודה האם המילה הראשונה היא alias ואם כן יבצע החלפה מתאימה כדי לבטל alias ניתן להשתמש בפקודה <alias> unalias < alias ll="ls l" > ll -rw-r--r-- 1 user staff 0 Nov 13 15:14 a.c > alias cdex2="cd ~/mtm/ex2" > cdex2 > alias GCC="gcc -std=c99 -Wall -Werror -pednatic-errors" > GCC hello.c -o hello > unalias GCC > GCC hello.c -o hello -bash:gcc: Command not found. 10
ניתן לרשום בפקודה תבנית המתייחסת למספר קבצים. במקרה זה bash יחליף את מחרוזת התבנית בפקודה ברשימת הקבצים המתאימים לתבנית הסימן הסימן * מתאים למחרוזת כלשהי? מתאים לתו כלשהו יחיד )כולל ריקה( ניתן להתייחס למספר תווים אפשריים על ידי שימוש ב-] ] ניתן לרשום את התווים האפשריים אחד אחרי השני במפורש ניתן לרשום טווח של תווים, a-z למשל ניתן להתייחס למספר מחרוזות שונות בעזרת { } אפשרות זו אינה מתחשבת בקבצים קיימים < ls axb.c a3b.c cab.txt a4b.txt > echo files: * files: axb.c a3b.c cab.txt a4b.txt > echo The source files are *.c The source files are axb.c a3b.c > echo a[0-9]b.* a3b.c a4b.txt > echo a3b.{c,txt} a3b.c a3b.txt 11
ניתן להציב ערך למשתנה ב- bash על ידי השמה ישירה > <varname>=<value> אין צורך להכריז על משתנים ב- bash, לאחר ביצוע השמה למשתנה הוא מוגדר אוטומטית ניתן להחליף ערך של משתנה על ידי השמה נוספת אסור לשים רווח בין סימן ההשמה לשם המשתנה והערך אם יהיה כזה רווח ה- Shell ינסה להריץ פקודה ששמה כשם המשתנה ניתן לקרוא משתנים על ידי שימוש באופרטור $, למשל: > echo $<varname> השימוש במשתנה הוא פשוט החלפת הקריאה למשתנה בערך השמור בו 12
ניתן לבטל הגדרה של משתנה על ידי הפקודה <varname> unset < unset my_variable בדרך כלל אין צורך בכך אם מנסים לקרוא משתנה שאינו מוגדר אז לא מודפס כלום > a=hell > echo $ao ניתן להשתמש ב-} } כדי לסמן מהו שם המשתנה לאופרטור $ מועיל כאשר השימוש במשתנה צמוד למחרוזת נוספת > echo ${a}o Hello 13
ניתן לשמור במשתנה יחיד מערך של מחרוזות על ידי שימוש בסוגריים > arr=(1 2 3) קריאת משתנה שקולה לקריאת האיבר הראשון )אינדקס 0( מהרשימה > echo $arr 1 קריאת כל המערך ניתנת מתבצעת על ידי שימוש ב-[*] ובסוגריים מסולסלים: > echo ${arr[*]{ 1 2 3 > echo $arr[*] 1[*] ניתן לגשת לאיבר יחיד בעזרת אופרטור [ ] ניתן להוסיף איברים חדשים למערך בשיטה זו > arr[3] = 4 > echo ${arr[*]{ 1 2 3 4 14
ניתן לקבל את מספר האיברים במערך בעזרת :${#<varname>[*]} ניתן לגשת לתחומים של איברים במערך במערך > echo ${#arr[*]} 4 > echo A:${arr[*]:0:2} A:1 2 > echo B:${arr[*]:1} B:2 3 4 > echo C:${arr[*]:2:1} C:3 המספר הראשון מציין את אינדקס ההתחלה של התחום המספר השני מציין את מספר האיסרים בתחום אם הוא אינו מופיע יודפסו כל האיברים החל מהאינדקס המבוקש 15
ניתן לשמור מחרוזות המכילות רווחים בעזרת שימוש בגרשיים כפולים > list=(matam "Hello world" 17) > echo ${#list[*]} : ${list[*]} 3 : Matam Hello world 17 > echo ${list[1]} Hello world קריאת משתנה שערכו מכיל רווחים תגרום לאיבוד הרווחים המדויקים שהיו בין המילים קודם לכן > str=" Very crazy spaces " > echo $str Very crazy spaces 16
ניתן להשתמש בגרשיים כפולים כדי לשמור על הרווחים בזמן ההחלפה > echo "$str" Very crazy spaces ניתן לקבל את מספר התווים במשתנה ע"י שימוש ב-#, אורכו של מערך בדומה לקבלת > echo ${#str} 29 17
ניתן להתייחס למשתנה רגיל כאל רשימה בעלת איבר יחיד > a=hello > echo ${a[0]} Hello > str="hello world" > echo ${str[1]} > list=(hello world) > echo ${list[1]} world נוכל לבטל את הרשימה או איבר אחד מתוך הרשימה > unset list[1] > echo ${list[*]} Hello בעזרת unset 18
בניגוד לשפת C, מחרוזות למשתנים ב- bash אין טיפוס. כל המשתנים הם מסוג מערך של > echo $((3 + 4)) 7 > n=$((3 * 7 + 4)) > echo $n 25 > a=5 > b=7 > sum=$(($a + $b)) > echo $sum 12 ניתן להחליף ביטוי אריתמטי בערכו על ידי $((<expression>)) הערך המספרי של המחרוזות ישמש בחישוב הביטוי משתנים יוחלפו בערכם )גם ללא $( אם אחד הארגומנטים אינו מספר, ערכו יהיה 0 בחישוב משתנה שאינו מוגדר, או ערכו אינו מספר יחושב כ- 0 > echo $(( Hello )) 0 > a=4 > echo $(( 2 + a )) 6 > str="hello world!" > num=17 > echo $(( $str * $num )) 0 19
ניתן לבצע את פעולות חשבוניות שונות בדומה לשפת C: פעולות חשבוניות פשוטות: +, -, * ו-/ השמות: = הגדלות והקטנות:,+=,-= -- ו-++ ניתן לבצע פעולות חשבוניות רק על מספרים שלמים ניתן להשתמש גם בפקודה let כדי לבצע פעולה חשבונית נוח כאשר רוצים להשתמש ב-++ ואין צורך בערך ההחזרה > let n=3+4 > let n++ > echo $n 8 > $((n++)) -bash: 9: command not found Bash מנסה לבצע פקודה בשם "9" ונכשל 20
Bash שומר את הפקודות האחרונות שהתבצעו ומאפשר ביצוען מחדש הדרך הכי פשוטה לגשת להיסטוריה היא על ידי שימוש ב- ו- ניתן לראות את הפקודות האחרונות שבוצעו על ידי הפקודה history ניתן לבצע את האחרונה על ידי!! ניתן לבצע את פקודה מספר n על ידי n! ניתן לבצע את הפקודה האחרונה שהתחילה ב-< string > על ידי!<string> ניתן לבצע את הפקודה האחרונה שמכילה!?<string> על ידי <string> אפשר להחליף str1 במחרוזת האחרונה ע"י בפקודה str2 > history 1 17:20 let a = 3 2 17:21 let b = 4 3 17:22 @ n = $b + $a 4 17:24 echo $n 5 17:28 history >!4 echo $n 7 >!! echo $n 7 >!let let b = 4 > ls /it/is/avery/long/path.^str1^str2^ החלפות המתבצעות בגלל גישה להיסטוריה מודפסות בזמן ביצוען ולפני ביצוע הפקודה > ^ls^cat^ cat /it/is/avery/long/path 21
ניתן "לשרשר" פקודות על ידי שימוש בגרשיים הפוכים ` באנגלית: backticks או backquotes יבצע תחילה את הפקודה בתוך ` ` ויחליף אותה בפלט שלה Bash > echo The length of $str is `echo -n $str wc -c` The length of Hello is 5 > grep c cow `cat farms_list.txt` farm1:3 farm2:2 > set a ="there are `ls wc -l` files in `pwd` " > echo $a there are 662 files in /usr/bin farms_list.txt farm1 farm2 22
גרשיים כפולים, " ", משמשים לשמירה על רווחים משתמשים ב-" " כאשר יש צורך לשמור מחרוזות שלמות במדויק > sentence="the dingo ate your baby" בתוך גרשיים אלו לא מתבצעות החלפות של תבניות בשמות הקבצים המתאימים > echo "*.c" : *.c *.c : main.c app.c hello.c > echo lets make some '$$' lets make some $$ גרשיים בודדים ' ', מונעים את כל ההחלפות בתחומם גרשיים הפוכים )backticks( מבצעים command substitution 23
עבודת ה- Shell מתבצעת בלולאה: הצגת prompt המתנה לפקודה עיבוד הפקודה עד לקבלת פקודה פשוטה ביצוע הפקודה לשם כך Bash מנהל מבני נתונים פנימיים אשר שומרים את ההיסטוריה, המשתנים וה- aliases שהוגדרו הטיפול בפקודה מתבצע בשלושה שלבים עיקריים 1. ניתוח הפקודה: הפקודה מופרדת למילים על פי רווחים וטאבים. התווים,> ו-< מפרידים בין פקודות 2. עיבוד הפקודה: ביצוע החלפות מחרוזות הפקודה עד לקבלת פקודה פשוטה 3. ביצוע הפקודה: שליחת הפקודה הפשוטה שהתקבלה לביצוע על ידי מערכת ההפעלה 24
בשלב זה מבצע bash החלפות במחרוזת הפקודה עד לקבלת פקודה פשוטה: אם הפקודה מכילה שימוש בפקודות קודמות )ע"י שימוש בסימן!( מתבצעת החלפה מההיסטוריה ומודפסת שורת הפקודה שנבחרה מההיסטוריה אם המילה הראשונה בפקודה הינה alias מתבצעת החלפה לפי ה- alias המוגדר החלפת ביטויים המתחילים ב-$ בערכי המשתנים המתאימים החלפת תבניות של שמות קבצים ברשימת הקבצים המתאימה החלפת פקודות המופיעות בתוך גרשיים הפוכים בתוצאת הפקודה.1.2.3.4.5 25
לאחר שהתקבלה פקודה סופית על bash להפעיל את הפקודה המתאימה, הפקודות מתחלקות שני סוגים: פקודות פנימיות של ה- Shell, למשל...,unset,let,cd פקודות חיצוניות - שמות קבצי הרצה, למשל...sort,cat,gcc,ls אם שם הפקודה מתאים לפקודה פנימית היא תיבחר אחרת ה- Shell יחפש בכל התיקיות המופיעות במשתנה path קובץ הרצה בשם המתאים, אם נרצה לאפשר ל- Shell להריץ תכניות מהתיקיה הנוכחית ללא שימוש ב-. ניתן להוסיף את התיקיה הנוכחית ל- PATH. התיקיות מופרדות ע"י סימן. ":" ניתן להשתמש בפקודה which כדי לגלות איזו תכנית תופעל עבור שם פקודה מסוים > PATH=${PATH[*]}:. > hello Hello world! > which hello./hello > which gcc /usr/bin/gcc < which cd Which: no cd in ( content of PATH ) 26
ל- Bash מגוון תוכנות מתקדמות המאפשרות למשתמש מתקדם לבצע פעולות מסובכות בקלות ניתן להתייחס למספר קבצים בבת אחת על ידי שימוש בתבניות ניתן להגדיר כינויים לפקודות ופרמטרים שכיחים בעזרת alias ניתן לקרוא לפקודות שהתבצעו בעבר הקרוב בעזרת ההיסטוריה ניתן לשמור ערכים במשתנים ולנצלם לפקודות ב- Bash כל המשתנים ב- bash הם מסוג רשימות של מחרוזות ניתן להשתמש ב-` ` כדי לבצע החלפת פקודה בפלט שלה בשורות פקודה ב- bash כל התכונות של bash מבוצעות על ידי החלפת מחרוזות פשוטה בשלב עיבוד הפקודה 27
הרצת תסריטים מבני בקרה ב- bash דוגמאות השוואה בין C ו- bash 28
נניח )מקרה היפותטי לחלוטין( שברשותנו קבצי בדיקה ופלט לתכנית שלנו וברצוננו לבדוק את נכונות התכנית מול קבצים אלו צריך לכתוב 3 פקודות לכל בדיקה > mtm_rentals < test1.in > tmpout 2> tmperr > diff expout1 tmpout > diff experr1 tmperr גם עם שימוש במנגנון ההיסטוריה הרצת הבדיקות מעיקה ולוקחת הרבה זמן מיותר הפתרון: אוטומטיזציה של הרצת הפקודות. לפיו יורצו כל הפקודות לפי הסדר ניצור קובץ אשר יכיל "תסריט" 29
ניתן להריץ קובץ המכיל פקודות Bash )להלן תסריט - )script על ידי הפקודה source הפקודות יבוצעו ב- Shell הנוכחי כאילו נכתבו בשורת הפקודה אחת אחרי השניה > source run_tests Running test 1 Running test 2 Running test 3 run_tests echo Running test 1 mtm_rentals < test1.in > tmpout 2> tmperr diff expout1 tmpout diff experr1 tmperr echo Running test 2 mtm_rentals < test2.in > tmpout 2> tmperr diff expout2 tmpout diff experr2 tmperr echo Running test 3 mtm_rentals < test3.in > tmpout 2> tmperr diff expout3 tmpout diff experr3 tmperr 30
בהתחברות של משתמש למערכת מורץ התסריט.login אשר בתיקית הבית של המשתמש בפתיחת Shell חדש של Bash מורץ התסריט.bashrc הרצות תסריטי האתחול מתבצעות באמצעות,source ולכן הן משפיעות על מצב ה- Shell.login # welcome message echo ------ Welcome `whoami`!-------- echo You are in `pwd` directory of \ `hostname` echo OS is `uname -s` # echo disk usage is `du -sh cut -f1` echo `who wc -l` users are logged in echo Today is `date`.bashrc PATH=$PATH:. alias ll="ls -l" alias cdex2="cd ~mtm/public/1011a/ex2" alias GCC="gcc -std=c99 -Wall \ -pedantic-errors -Werror" 31
ניתן להריץ תסריט כפקודה: בתחילת התסריט יש להוסיף את השורה #!/bin/bash!# מסמן ל- Unix שהשורה הראשונה בקובץ מגדירה את התכנית לביצוע שאר הפקודות בקובץ /bin/bash הוא שם התכנית לביצוע הפקודות, במקרה שלנו Bash בנוסף יש להוסיף הרשאת ריצה לקובץ כעת ניתן להריץ את התסריט כמו תכנית רגילה בניגוד להרצה באמצעות פקודת source התסריט יבוצע בתהליך Shell חדש אשר יסתיים בסוף ריצת התסריט 32
run_tests #!/bin/bash echo Running test 1 mtm_rentals < test1.in > tmpout 2> tmperr diff expout1 tmpout diff experr1 tmperr echo Running test 2 mtm_rentals < test2.in > tmpout 2> tmperr diff expout2 tmpout diff experr2 tmperr echo Running test 3 mtm_rentals < test3.in > tmpout 2> tmperr diff expout3 tmpout diff experr3 tmperr נמיר את תסריט הרצת הבדיקות שלנו לקובץ הרצה: בעיה חדשה: התסריט מתחיל להסתבך, הוספת בדיקות נוספות משכפלת קוד בתסריט ולא נוחה )ייתכנו אלפי בדיקות( פתרון: נשתמש במבני בקרה )תנאים ולולאות( בדומה לשפת C > chmod a+x run_tests >./run_tests Running test 1 Running test 2 Running test 3 33
while מאפשרת שימוש במבני בקרה )למשל Bash הפקודה ו- if ( בתסריטים ובשורת מבני הבקרה משתמשים בתנאים בדומה למבני הבקרה שבשפת C > for ((i = 1; $i < 10; i++ )); do > if (($i % 3 == 0)); then > echo 3 divides $i > fi > done 3 divides 3 3 divides 6 3 divides 9 השוואת ערכי מספרים השוואת מחרוזות ועוד... נעבור על מספר מבני בקרה ותנאים שימושיים 34
while <expression>; do <command1> <command2>... done > i=1 > while (( i <= 3)); do > echo $i > let i++ > done 1 2 3 35 ניתן ליצור לולאות while ב- bash : הפקודות בגוף הלולאה יבוצעו כל עוד <expression> ממשיך להתקיים לולאות while כמו שאר מבני הבקרה ניתנות לביצוע ישירות מהטרמינל run_tests #!/bin/bash i=1 while (( i <= 3)); do echo Running test $i mtm_rentals < test${i}.in \ > tmpout 2> tmperr diff expout${i} tmpout diff experr${i} tmperr let i++ done >./run_tests Running test 1 Running test 2 Running test 3
שימוש נוסף ב- for הוא מעבר על על איברי המערך: for <varname> in <array> ; do <command1> <command2>... done <varname> הוא שם המשתנה שיכיל בכל פעם איבר מהרשימה <array> היא רשימה של מחרוזות העדיפו להשתמש בשיטה זו על פני שימוש מפורש באינדקסים 36
לולאת C משמשת בדומה לשפת for למעבר נוח יותר על תחום מספרים run_tests #!/bin/bash for ((i = 1; i <= 3; i++)); do echo Running test$i mtm_rentals < test${i}.in > tmpout 2> tmperr diff expout$i tmpout diff experr$i tmperr done >./run_tests Running test1.in Running test2.in Running test3.in 37
if <expression>; then <command1> <command2>... fi if <expression>; then <commands> else <commands> fi ניתן להגדיר ב- bash משפטי תנאי בשתי גרסאות ניתן להוסיף else ופקודות אשר יתבצעו אם התנאי אינו מתקיים > i=1 > if (( $i > 0 )); then > echo POSITIVE > else > echo NEGATIVE > fi POSITIVE 38
קיימות מספר אפשרויות לכתיבת התנאים בלולאות ומשפטי תנאי ניתן לרשום תנאים בתוך ]] [[: האופרטורים ==, > ו-> משווים מחרוזות )לפי סדר לקסיקוגרפי( ניתן לבצע השוואת על ערכי מספרים בעזרת דגלים כגון -le,-gt,-eq חשוב להקפיד על תו הרווח בין הסוגריים לתוכן התנאי כדי למנוע שגיאות ב- bash > str1=hello > if [[ $str1 == Hello ]]; then echo true; fi true > if [[ hello == Hello ]]; then echo true; fi > if [[ 11 < 7 ]]; then echo true; fi true > if [[ 11 -le 7 ]]; then echo true; fi > if [[ 11 -eq 11 ]]; then echo true; fi true > if [[ 0 -eq Hello ]]; then echo true; fi true זו אינה השוואת ערכים מספריים עבור ערך שאינו מייצג מספר משתמשים ב- 0 לחישוב 39
בתוך ]] [[ ניתן להשתמש בתנאים מועילים נוספים, למשל: התנאי <filename> f- בודק האם קיים קובץ בשם <filename> התנאי <dirname> d- בודק האם קיימת תיקייה בשם <dirname> ניתן לבדוק תנאים מורכבים יותר: ניתן להשתמש באופרטורים &&,, ו-! כמו בשפת C הקפידו להשאיר רווח אחרי! כדי להימנע משגיאות בגלל מנגנון ההיסטוריה ניתן להשתמש בסוגריים כדי לקבוע את הקדימויות > if [[ -f a.txt ]]; then echo file exists; fi > cat > a.txt Hello world! > if [[ -f a.txt ]]; then echo file exists; fi file exists > mkdir mtm > if [[! (-f b.txt && -d mtm) ]]; then echo yes; fi yes 40
בתוך תנאי מהצורה ]] [[, האופרטור = מאפשר התאמת מחרוזת לתבנית הארגומנט השמאלי הוא מחרוזת רגילה הארגומנט הימני הוא תבנית אשר יכולה לכלול את הסימנים *,? ו-] ] כמו שתואר עבור תבניות של שמות קבצים האופרטור =! הוא השלילה של אופרטור ההתאמה = שימו לב שהמשמעות של = ו-== שונה > end_with_z="some string with z" > if [[ "$end_with_z" = *[zz] ]]; then echo match; fi match > if [[ "this string start with t" = t* ]]; then echo true; fi true > if [[ "this string doesn't start with t" = [^t]* ]]; then echo true; fi > file=test4.in > if [[ $file = test*.in ]]; then echo test file; fi test file > if [[ "string doesn't start with t"!= t* ]]; then echo true; fi true 41
ניתן להגדיר תנאי בתוך )) (( בתוך (( )) האופרטורים,==,!= > ו- < מתייחסים לערכים מספריים אין צורך לרשום $ לפני שם משתנה ניתן לבצע פעולות חשבוניות $)) (( תנאים המוגדרים בעזרת )) (( מתנהגים כמו ביצוע פעולות חשבוניות בעזרת > if (( 11 < 7 )); then echo true; fi > i=5 > if (( i >= 0 && i <= 10 )); then echo true; fi true > if [[ 11 -eq 11 ]]; then echo true; fi true > if (( 0 == Hello )); then echo true; fi true > if (( ++i == 6 )); then echo true; fi true > if (( ++i == 6 )); then echo true; fi 42
כמו ב- C הפרמטר ה- n ניתן לגשת לפרמטרים המועברים בשורת הפקודה לתסריט לתסריט נקרא פשוט n, יוחלף ברשימת כל הארגומנטים לתסריט וניתן לקרוא אותו על ידי n$ אם ייתכנו רווחים בתוך הארגומנטים ניתן להשתמש ב-"@$" כדי לשמור על מספר האגומנטים הנכון echo_script #!/bin/bash echo command: $0 echo $# arguments let number=1 for param in $*; do echo parameter $((number++)) : $param done יוחלף בשם התסריט יוחלף במספר הארגומנטים $* $0 $# > echo_script aaa bbb ccc command: echo_script 3 arguments parameter 1 : aaa parameter 2 : bbb parameter 3 : ccc 43
function <name> { } <commands> ניתן להגדיר ב- bash פונקציות בצורה כזו: פונקציה חייבת להכיל לפחות פקודה אחת השימוש בפונקציה יכול להתבצע רק אחרי הגדרתה אפשר להעביר לפונקציה ארגומנטים הפונקציה משתמשת בהם בדומה לשימוש בארגומנטים המועברים לתסריט הפונקציה אינה יכולה לגשת לארגומנטים של התסריט שקרא לה 44
כדי ליצור משתנה מקומי בפונקציה יש להכריז עליו תחילה עם :local using_local #!/bin/bash function surprise { local a=surprise_a b=surprise_b } a=original_a b=original_b echo $a $b surprise echo $a $b > using_local original_a original_b original_a surprise_b 45
:)backticks( command subsitution ניתן "להחזיר" ערכים מפונקציה בעזרת sum_numbers #!/bin/bash function sum { local result=0 for num in $*; do let result+=$num done echo $result } n=`sum $*` echo $n ביטוי זה יוחלף בסכום הרצוי > sum_numbers 1 2 3 4 5 15 46
ניתן לקרוא שורה מהקלט הסטנדרטי על ידי name> read <flags> <variable השורה תיקלט לתוך שם המשתנה שהוגדר הדגל a- יחלק את השורה לפי מילים לתוך מערך הביטוי read יוחלף על ידי bash בשורת קלט שתיקלט מהקלט הסטנדרטי > read line Hello world > echo $line Hello world > read -a line Hello world > echo $line Hello > echo ${line[*]} Hello world 47
דרך פשוטה לקריאת קובץ היא על ידי שימוש בהפניית קלט ושימוש ב- read : > cat hello.txt Hello world! > read line < hello.txt > echo $line Hello world! lines_counter #!/bin/bash counter=0 while read line; do echo $line let counter++ done < "$1" echo $counter דרך פשוטה לקרוא קובץ שורה אחר שורה היא כך: > cat lines.txt first line second line > lines_counter lines.txt first line second line 2 48
שימו לב שניתן להשתמש ב- bash כדי לערבב תסריטים עם תכניות שנכתבו בשפות שונות בקלות מכאן מגיע כוחן של שפות תסריטים - languages scripting ניתן להעביר פרמטרים לתסריטי עזר בעזרת פרמטרים בשרות הפקודה בעזרת pipeline בעזרת קבצים זמניים ניתן לקבל ערכים חזרה מתסריטי עזר בעזרת פלט מתסריט העזר 49
העברת פרמטרים לתסריטי עזר יכולה להתבצע בדרכים הבאות בשורת הפקודה החזרת ערכים מתסריטי העזר יכולה להתבצע בעזרת שימוש ב- backticks set result = `helper_script` helper_script $arg1 $arg2 העברה לתסריט אחר ב- pipeline helper_script another_script בעזרת pipeline echo $arg1 $arg2 helper_script דרך קובץ זמני דרך קובץ זמני helper_script > temp echo $arg1 $arg2 > temp helper_script < temp 50
כתבו תסריט בשם search אשר מקבל מחרוזת ושמות קבצים ומדפיס את כל השורות המופיעות בקבצים הללו המכילות את המחרוזת המבוקשת אם מתקבל שם תיקיה, ייבדקו כל הקבצים תחת התיקיה הזו רקורסיבית > search Blue scene35.txt scene35.txt : 37 : LAUNCELOT: Blue. scene35.txt : 55 : GALAHAD: Blue. No yel-- Auuuuuuuugh! > search swallow scene*.txt scene1.txt : 50 : GUARD #1: But then of course African swallows are not migratory. scene1.txt : 54 : GUARD #2: Wait a minute -- supposing two swallows carried it together? scene35.txt : 63 : BEDEMIR: How do know so much about swallows? > search cow farms farms/animals/animals.txt : 8 : cow farms/farm1.txt : 2 : cow Betsy farms/farm1.txt : 3 : slim cow Dazy farms/farm1.txt : 4 : fat cow Burger farms/farm1.txt : 5 : two cows Dartsy & Teo farms/farm2.txt : 2 : cow Leni farms/farm2.txt : 4 : cow Oreo 51
search #!/bin/bash function search_file { n=1 while read line; do if [[ $line = *"$1"* ]]; then echo ${2} : ${n} : $line fi let n++; done < "$2" } for file in ${*:2}; do if [[ -f "$2" ]]; then search_file "$1" $file fi if [[ -d "$2" ]]; then search "$1" $file/* fi done נכתוב תסריט בשם search התסריט ישתמש בפונקצית עזר הקוראת קובץ ומוצאת את השורות המתאימות בעזרת התאמת מחרוזות 52
football.txt Alon Miz. 2 23/10/93 Macabi-Haifa Macabi-Tel-Aviv Izak Zoh. 1 12/11/93 Macabi-Tel-Aviv Hapoel-Beer-Sheva Ronen Ha. 3 27/12/93 Hapoel-Tel-Aviv Macabi-Tel-Aviv Reuven A. 2 12/11/93 Macabi-Haifa Hapoel-Tel-Aviv Eyal Ber. 1 20/11/93 Macabi-Haifa Macabi-Tel-Aviv Izak Zoh. 1 12/11/93 Macabi-Tel-Aviv Hapoel-Haifa Alon Miz. 2 26/10/93 Macabi-Haifa Beitar-Jerusalem Izak Zoh. 2 12/12/93 Macabi-Tel-Aviv Macabi-Hiafa Alon Miz. 2 23/12/93 Macabi-Haifa Macabi-Pet-Tikva Ronen Ha. 3 27/11/93 Hapoel-Tel-Aviv Macabi-Haifa נתון קובץ בשם football.txt המכיל נתונים על שערים שהובקעו במשחקי כדורגל כל שורה בקובץ מציינת שם של שחקן, מספר השערים שהבקיע במשחק שנערך בתאריך מסוים, שם הקבוצה בה הוא שיחק ושם הקבוצה היריבה > player "Alon Miz." Alon Miz. 2 23/10/93 Macabi-Haifa Macabi-Tel-Aviv Alon Miz. 2 26/10/93 Macabi-Haifa Beitar-Jerusalem Alon Miz. 2 23/12/93 Macabi-Haifa Macabi-Pet-Tikva Total number of goals: 6 ברצוננו לכתוב תסריט בשם player אשר יקבל כפרמטר שם של שחקן וידפיס את כל השורות עבורו מהקובץ football.txt ואת סכום מספר השערים שהבקיע 53
player #!/bin/bash function calc_total { sum=0 while read -a line; do let sum+=${line[6]} echo ${line[*]:4} done echo "total number of goals: $sum" } search "$1" football.txt calc_total נשתמש בתסריט search שכתבנו מקודם כדי לקבל רק את השורות הרלוונטיות נכתוב פונקצית עזר בשם calc_total אשר תקרא את השורות מהקלט הסטנדרטי שלה ותסכום את מספר הגולים של השחקן תוך כדי הדפסתן 54
כתבו תסריט לדירוג שחקנים בשם best_player אשר יקבל רשימה של שמות שחקנים בשורת הפקודה וידפיס את שם השחקן שהבקיע את מרב הגולים אם קיימים מספר שחקנים שהבקיעו את מרב הגולים יודפסו שמות כל השחקנים לכל שחקן יש להדפיס את שמו ומספר הגולים שהבקיע > best_player "Alon Miz." "Izak Zoh." "Ronen Ha." "Reuven A." Alon Miz. 6 Ronen Ha. 6 55
best_player #!/bin/bash # Sums the goals from lines in # the correct format function sum_goals { local sum=0 while read -a line; do let sum+=${line[6]} done echo $sum } # Sums all the goals of target player function sum_player_goals {./search "$1" football.txt sum_goals } max_goals=0 for player in "$@"; do goals=`sum_player_goals "$player"` if (( goals > max_goals )); then max_goals=$goals fi done for player in "$@"; do goals=`sum_player_goals "$player"` if (( goals >= max_goals )); then echo "$player" $max_goals fi done 56
scripting language - היא שפת תסריטים Bash שפות תסריטים נוספות:.Ruby,Python,Tcl,Perl system programming language - היא שפת תכנות מערכת C שפות מערכת נוספות:,C++.C#,Java 57
יתרונות של bash על C: עבודה נוחה עם מחרוזות ומשתנים נוח "להדביק" תכניות קיימות )עוד על כך בתרגול 7( קוד קצר משמעותית לחלק מהמשימות אין קומפיילר - לא צריך להכריז על דברים read deposits account_balance=100 for d in ${deposits[*]}; do acount_balance=$((account_balance + d)) done echo $account_balance חסרונות של bash לעומת C: אין קומפיילר - אין בדיקות מאפשר באגים מסוכנים איטית )לעתים פי כמה מאות( נבחר ב- bash עבור מטלות פשוטות וקצרות שזמן הביצוע שלהן לא קריטי 58
כדי לחסוך ביצוע חוזר וידני של פעולות ניתן ליצור תסריטים המכילים רצף פקודות שימושי ולהריצם בעזרת source או הפיכתם לקובץ הרצה ב- bash קיימים מבני הבקרה בתסריטים for ו,while if המאפשרים כתיבת קוד מתקדם ניתן לגשת לפרמטרים לשרות הפקודה של תסריט בדומה לתכנית ב- C כדי לקרוא מהקלט הסטנדרטי ב- bash נשתמש בפקודה read ניתן לחלק תסריטים לפונקציות ולהעביר מידע ביניהן ע"י קריאת טקסט בפונקציה וכתיבתו לפלט נוח להדביק תסריטים ביחד בעזרת pipeline ו- substitution command כדי לפתור בקלות בעיות נשתמש ב- bash עבור מטלות פשוטות שאינן דורשות חישובים רבים, עבור שאר המטלות נמשיך להשתמש ב- C 59